﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using MVVMFramework.Core.Model;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace MVVMFramework.Core.ViewModel.Collections
{
    public class DomainCollectionView<T> : ViewModelBase, IDomainCollectionView<T>
    {
        private IDomainQuery<T> query;
        private ObservableCollection<T> sourceCollection;

        #region DomainCollectionView Members

        public DomainCollectionView(IDomainQuery<T> query, bool usePaging = true)
        {
            this.query = query;
            this.sourceCollection = this.CreateSourceCollection();
            this.CurrentItem = null;
            this.CurrentPosition = -1;

            //Apply sort and paging
            query.SortDescriptions = () => this.SortDescriptions;
            if (usePaging)
            {
                this.canChangePage = true;
                this.pageSize = 10;// default page size to 10
                this.pageIndex = 0;
                query.PageDescription = () => new PageDescription { PageSize = this.PageSize, PageIndex = this.PageIndex };
            }

            query.Loaded += this.OnDataRefreshed;
        }

        private ObservableCollection<T> CreateSourceCollection()
        {
            var source = new ObservableCollection<T>();

            source.CollectionChanged += (s, args) =>
            {
                if (this.isInDeferRefresh)
                    return;

                var ev = this.CollectionChanged;
                if (ev != null)
                    ev(this, args);
            };

            return source;
        }

        private void RefreshData()
        {
            if (this.query.IsLoading)
                this.query.Cancel();

            this.query.Load();
        }

        private void OnDataRefreshed(ILoadOperation<T> loadOp)
        {
            using (this.DeferRefresh())
            {
                this.sourceCollection.Clear();
                foreach (var entity in loadOp.Entities)
                    this.sourceCollection.Add(entity);

                this.ItemCount = loadOp.TotalEntityCount;
                this.TotalItemCount = loadOp.TotalEntityCount;
            }
        }

        #endregion

        #region IDomainCollectionView<T> Members

        public void Add(T item)
        {
            this.sourceCollection.Add(item);
        }

        public void Remove(T item)
        {
            this.sourceCollection.Remove(item);

            if (this.IsEmpty)
            {
                this.CurrentItem = null;
                this.CurrentPosition = -1;

                var currentChangingEv = this.CurrentChanging;
                if (currentChangingEv != null)
                    currentChangingEv(this, new CurrentChangingEventArgs(false));

                this.RaisePropertyChanged(() => this.CurrentItem);
                this.RaisePropertyChanged(() => this.CurrentPosition);
                this.RaisePropertyChanged(() => this.IsCurrentBeforeFirst);

                var currentChangedEv = this.CurrentChanged;
                if (currentChangedEv != null)
                    currentChangedEv(this, EventArgs.Empty);
            }
            else
            {
                var count = this.Count;
                int newPos = this.CurrentPosition < count ? this.CurrentPosition : count - 1;
                this.MoveCurrentToPosition(newPos);
            }
        }

        #endregion

        #region IDomainCollectionView Members

        public void Add(object item)
        {
            this.Add((T)item);
        }

        public void Remove(object item)
        {
            this.Remove((T)item);
        }

        public int Count
        {
            get
            {
                return this.sourceCollection.Count;
            }
        }

        #endregion

        #region ICollectionView Members

        public bool CanFilter
        {
            get { return false; }
        }

        public bool CanGroup
        {
            get { return false; }
        }

        public bool CanSort
        {
            get { return true; }
        }

        public bool Contains(object item)
        {
            if (item is T)
                return this.sourceCollection.Contains((T)item);
            return false;
        }

        public System.Globalization.CultureInfo Culture
        {
            get
            {
                return System.Threading.Thread.CurrentThread.CurrentCulture;
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        public event EventHandler CurrentChanged;

        public event System.ComponentModel.CurrentChangingEventHandler CurrentChanging;

        public object CurrentItem
        {
            get;
            private set;
        }

        public int CurrentPosition
        {
            get;
            private set;
        }

        bool isInDeferRefresh;
        public IDisposable DeferRefresh()
        {
            this.isInDeferRefresh = true;
            return new DeferRefreshHelper(() =>
            {
                this.isInDeferRefresh = false;
                this.Refresh();
            });
        }

        public Predicate<object> Filter
        {
            get
            {
                return null;
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        public System.Collections.ObjectModel.ObservableCollection<System.ComponentModel.GroupDescription> GroupDescriptions
        {
            get { return null; }
        }

        public System.Collections.ObjectModel.ReadOnlyObservableCollection<object> Groups
        {
            get { return null; }
        }

        public bool IsCurrentAfterLast
        {
            get { return this.CurrentItem == null && (this.Count == 0 || this.CurrentPosition >= this.Count); }
        }

        public bool IsCurrentBeforeFirst
        {
            get { return this.CurrentItem == null && this.CurrentPosition == -1; }
        }

        public bool IsEmpty
        {
            get { return this.sourceCollection.Count == 0; }
        }

        public bool MoveCurrentTo(object item)
        {
            if (item is T)
            {
                var itemToSelect = (T)item;
                if (itemToSelect == null)
                    return this.MoveCurrentToPosition(-1);
                else if (this.sourceCollection.Contains(itemToSelect))
                    return this.MoveCurrentToPosition(this.sourceCollection.IndexOf(itemToSelect));
            }

            return false;
        }

        public bool MoveCurrentToFirst()
        {
            if (this.CurrentPosition == 0)
                return false;

            return this.MoveCurrentToPosition(0);
        }

        public bool MoveCurrentToLast()
        {
            if (this.CurrentPosition == this.Count - 1)
                return false;
            return this.MoveCurrentToPosition(this.Count - 1);
        }

        public bool MoveCurrentToNext()
        {
            if (this.CurrentPosition == this.Count - 1)
                return false;
            return this.MoveCurrentToPosition(this.CurrentPosition + 1);
        }

        public bool MoveCurrentToPosition(int position)
        {
            if (position == this.CurrentPosition)
                return false;

            if (this.IsEmpty)
            {
                Debug.Assert(this.CurrentItem == null); //this must be true
                Debug.Assert(this.CurrentPosition == -1); //this must be true
                return false;
            }

            var changingArgs = new CurrentChangingEventArgs(true);
            var currentChangingEv = this.CurrentChanging;
            if (currentChangingEv != null)
                currentChangingEv(this, changingArgs);

            if (changingArgs.Cancel)
            {
                return false;
            }

            if (position == -1)
            {
                this.CurrentItem = null;
            }
            else
            {
                this.CurrentItem = this.sourceCollection.ElementAt(position);
            }

            this.CurrentPosition = position;

            this.RaisePropertyChanged(() => this.CurrentItem);
            this.RaisePropertyChanged(() => this.CurrentPosition);
            this.RaisePropertyChanged(() => this.IsCurrentBeforeFirst);

            var currentChangedEv = this.CurrentChanged;
            if (currentChangedEv != null)
                currentChangedEv(this, EventArgs.Empty);

            return true;

        }

        public bool MoveCurrentToPrevious()
        {
            if (this.CurrentPosition == 0)
                return false;

            return this.MoveCurrentToPosition(this.CurrentPosition - 1);
        }

        public void Refresh()
        {
            if (this.isInDeferRefresh)
                throw new InvalidOperationException();

            var ev = this.CollectionChanged;
            if (ev != null)
                ev(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }


        private SortDescriptionCollection sortDescriptions;
        public System.ComponentModel.SortDescriptionCollection SortDescriptions
        {
            get
            {
                if (this.sortDescriptions == null)
                {
                    this.sortDescriptions = new SortDescriptionCollection();
                    (this.sortDescriptions as INotifyCollectionChanged).CollectionChanged += (s, e) =>
                    {
                        if (e.Action != NotifyCollectionChangedAction.Reset)
                            this.RefreshData();
                    };
                }
                return this.sortDescriptions;
            }
        }

        public System.Collections.IEnumerable SourceCollection
        {
            get { return this.sourceCollection; }
        }

        #endregion

        #region IEnumerable Members

        public System.Collections.IEnumerator GetEnumerator()
        {
            return this.sourceCollection.GetEnumerator();
        }

        #endregion

        #region INotifyCollectionChanged Members

        public event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region IPagedCollectionView Members

        private bool canChangePage;
        public bool CanChangePage
        {
            get { return this.canChangePage; }
            set
            {
                this.canChangePage = value;
                this.RaisePropertyChanged(() => this.CanChangePage);
            }
        }
        
        private bool isPageChanging;
        public bool IsPageChanging
        {
            get { return isPageChanging; }
            private set
            {
                if (isPageChanging != value)
                {
                    isPageChanging = value;
                    this.RaisePropertyChanged(() => this.IsPageChanging);
                }
            }
        }

        private int itemCount;
        public int ItemCount
        {
            get { return itemCount; }
            set
            {
                if (itemCount != value)
                {
                    itemCount = value;
                    this.RaisePropertyChanged(() => this.ItemCount);
                }
            }
        }

        public bool MoveToFirstPage()
        {
            return MoveToPage(0);
        }

        public bool MoveToLastPage()
        {
            int index = TotalItemCount % PageSize == 0 ? (TotalItemCount / PageSize) - 1 : TotalItemCount / PageSize;
            return MoveToPage(index);
        }

        public bool MoveToNextPage()
        {
            return MoveToPage(PageIndex + 1);
        }

        public bool MoveToPage(int index)
        {
            if (index == PageIndex || index < 0 || index > TotalItemCount / PageSize)
                return false;

            if (index == TotalItemCount / PageSize && TotalItemCount % PageSize == 0)
                return false;

            PageChangingEventArgs args = new PageChangingEventArgs(index);

            try
            {
                IsPageChanging = true;
                var pageChangingEv = this.PageChanging;
                if (pageChangingEv != null)
                    pageChangingEv(this, args);

                if (!args.Cancel)
                {
                    pageIndex = index;
                    RefreshData();

                    this.RaisePropertyChanged(() => this.PageIndex);
                    var pageChangedEv = this.PageChanged;
                    if (pageChangedEv != null)
                        pageChangedEv(this, EventArgs.Empty);

                    return true;
                }

                return false;
            }
            finally
            {
                IsPageChanging = false;
            }
        }

        public bool MoveToPreviousPage()
        {
            return MoveToPage(PageIndex - 1);
        }

        public event EventHandler<EventArgs> PageChanged;

        public event EventHandler<System.ComponentModel.PageChangingEventArgs> PageChanging;

        private int pageIndex;
        public int PageIndex
        {
            get { return pageIndex; }
            set
            {
                if (pageIndex < 0 || pageIndex > totalItemCount / PageSize)
                {
                    throw new ArgumentOutOfRangeException("PageIndex must be greater than or equal to 0 and less than the page count");
                }

                MoveToPage(value);
            }
        }

        private int pageSize;
        public int PageSize
        {
            get { return pageSize; }
            set
            {
                if (pageSize != value)
                {
                    pageSize = value;
                    this.RaisePropertyChanged(() => this.PageSize);
                    RefreshData();
                }
            }
        }

        int totalItemCount;
        public int TotalItemCount
        {
            get { return totalItemCount; }
            set
            {
                if (totalItemCount != value)
                {
                    totalItemCount = value;
                    this.RaisePropertyChanged(() => this.TotalItemCount);
                }
            }
        }

        #endregion

        #region IEnumerable<T> Members

        System.Collections.Generic.IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator()
        {
            return this.sourceCollection.GetEnumerator();
        }

        #endregion
    }

    class DeferRefreshHelper : IDisposable
    {
        private Action _callback;

        public DeferRefreshHelper(Action callback)
        {
            _callback = callback;
        }

        public void Dispose()
        {
            _callback();
        }
    }
}
